/*
 * %W% %E%
 *
 * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package java.security.cert;

import java.io.IOException;
import java.math.BigInteger;
import java.security.PublicKey;
import java.util.*;
import javax.security.auth.x500.X500Principal;

import sun.misc.HexDumpEncoder;
import sun.security.util.Debug;
import sun.security.util.DerInputStream;
import sun.security.util.DerValue;
import sun.security.util.ObjectIdentifier;
import sun.security.x509.*;

/**
 * A <code>CertSelector</code> that selects <code>X509Certificates</code> that
 * match all specified criteria. This class is particularly useful when
 * selecting certificates from a <code>CertStore</code> to build a
 * PKIX-compliant certification path.
 * <p>
 * When first constructed, an <code>X509CertSelector</code> has no criteria
 * enabled and each of the <code>get</code> methods return a default value
 * (<code>null</code>, or <code>-1</code> for the {@link #getBasicConstraints
 * getBasicConstraints} method). Therefore, the {@link #match match} 
 * method would return <code>true</code> for any <code>X509Certificate</code>. 
 * Typically, several criteria are enabled (by calling 
 * {@link #setIssuer setIssuer} or
 * {@link #setKeyUsage setKeyUsage}, for instance) and then the
 * <code>X509CertSelector</code> is passed to
 * {@link CertStore#getCertificates CertStore.getCertificates} or some similar
 * method.
 * <p>
 * Several criteria can be enabled (by calling {@link #setIssuer setIssuer}
 * and {@link #setSerialNumber setSerialNumber},
 * for example) such that the <code>match</code> method 
 * usually uniquely matches a single <code>X509Certificate</code>. We say
 * usually, since it is possible for two issuing CAs to have the same
 * distinguished name and each issue a certificate with the same serial
 * number. Other unique combinations include the issuer, subject, 
 * subjectKeyIdentifier and/or the subjectPublicKey criteria.
 * <p>
 * Please refer to RFC 2459 for definitions of the X.509 certificate
 * extensions mentioned below.
 * <p>
 * <b>Concurrent Access</b>
 * <p>
 * Unless otherwise specified, the methods defined in this class are not
 * thread-safe. Multiple threads that need to access a single
 * object concurrently should synchronize amongst themselves and
 * provide the necessary locking. Multiple threads each manipulating
 * separate objects need not synchronize.
 *
 * @see CertSelector
 * @see X509Certificate
 *
 * @version 	%I%, %G%
 * @since	1.4
 * @author	Steve Hanna
 */
public class X509CertSelector implements CertSelector {

    private static final Debug debug = Debug.getInstance("certpath");
    
    private final static ObjectIdentifier ANY_EXTENDED_KEY_USAGE = 
	ObjectIdentifier.newInternal(new int[] {2, 5, 29, 37, 0});
    
    static {
	CertPathHelperImpl.initialize();
    }
  
    private BigInteger serialNumber;
    private X500Principal issuer;
    private X500Principal subject;
    private byte[] subjectKeyID;
    private byte[] authorityKeyID;
    private Date certificateValid;
    private Date privateKeyValid;
    private ObjectIdentifier subjectPublicKeyAlgID;
    private PublicKey subjectPublicKey;
    private byte[] subjectPublicKeyBytes;
    private boolean[] keyUsage;
    private Set<String> keyPurposeSet;
    private Set<ObjectIdentifier> keyPurposeOIDSet;
    private Set<List<?>> subjectAlternativeNames;
    private Set<GeneralNameInterface> subjectAlternativeGeneralNames;
    private CertificatePolicySet policy;
    private Set<String> policySet;
    private Set<List<?>> pathToNames;
    private Set<GeneralNameInterface> pathToGeneralNames;
    private NameConstraintsExtension nc;
    private byte[] ncBytes;
    private int basicConstraints = -1;
    private X509Certificate x509Cert;
    private boolean matchAllSubjectAltNames = true;

    private static final Boolean FALSE = Boolean.FALSE;

    private static final int PRIVATE_KEY_USAGE_ID = 0;
    private static final int SUBJECT_ALT_NAME_ID = 1;
    private static final int NAME_CONSTRAINTS_ID = 2;  
    private static final int CERT_POLICIES_ID = 3; 
    private static final int EXTENDED_KEY_USAGE_ID = 4;
    private static final int NUM_OF_EXTENSIONS = 5;
    private static final String[] EXTENSION_OIDS = new String[NUM_OF_EXTENSIONS];
    
    static {
	EXTENSION_OIDS[PRIVATE_KEY_USAGE_ID]  = "2.5.29.16";
	EXTENSION_OIDS[SUBJECT_ALT_NAME_ID]   = "2.5.29.17";
	EXTENSION_OIDS[NAME_CONSTRAINTS_ID]   = "2.5.29.30";
	EXTENSION_OIDS[CERT_POLICIES_ID]      = "2.5.29.32";
	EXTENSION_OIDS[EXTENDED_KEY_USAGE_ID] = "2.5.29.37";
    };
    
    /* Constants representing the GeneralName types */
    static final int NAME_ANY = 0;
    static final int NAME_RFC822 = 1;
    static final int NAME_DNS = 2;
    static final int NAME_X400 = 3;
    static final int NAME_DIRECTORY = 4;
    static final int NAME_EDI = 5;
    static final int NAME_URI = 6;
    static final int NAME_IP = 7;
    static final int NAME_OID = 8;

    /**
     * Creates an <code>X509CertSelector</code>. Initially, no criteria are set
     * so any <code>X509Certificate</code> will match.
     */
    public X509CertSelector() {
	// empty
    }
 
    /**
     * Sets the certificateEquals criterion. The specified 
     * <code>X509Certificate</code> must be equal to the 
     * <code>X509Certificate</code> passed to the <code>match</code> method.
     * If <code>null</code>, then this check is not applied.
     *
     * <p>This method is particularly useful when it is necessary to
     * match a single certificate. Although other criteria can be specified 
     * in conjunction with the certificateEquals criterion, it is usually not 
     * practical or necessary.
     *
     * @param cert the <code>X509Certificate</code> to match (or 
     * <code>null</code>)
     * @see #getCertificate
     */
    public void setCertificate(X509Certificate cert) {
	x509Cert = cert;
    }

    /**
     * Sets the serialNumber criterion. The specified serial number
     * must match the certificate serial number in the
     * <code>X509Certificate</code>. If <code>null</code>, any certificate 
     * serial number will do.
     *
     * @param serial the certificate serial number to match 
     *        (or <code>null</code>)
     * @see #getSerialNumber
     */
    public void setSerialNumber(BigInteger serial) {
	serialNumber = serial;
    }
  
    /**
     * Sets the issuer criterion. The specified distinguished name
     * must match the issuer distinguished name in the
     * <code>X509Certificate</code>. If <code>null</code>, any issuer
     * distinguished name will do.
     *
     * @param issuer a distinguished name as X500Principal
     *                 (or <code>null</code>)
     * @since 1.5
     */
    public void setIssuer(X500Principal issuer) {
	this.issuer = issuer;
    }

    /**
     * <strong>Denigrated</strong>, use {@linkplain #setIssuer(X500Principal)} 
     * or {@linkplain #setIssuer(byte[])} instead. This method should not be 
     * relied on as it can fail to match some certificates because of a loss of 
     * encoding information in the RFC 2253 String form of some distinguished
     * names.
     * <p>
     * Sets the issuer criterion. The specified distinguished name
     * must match the issuer distinguished name in the
     * <code>X509Certificate</code>. If <code>null</code>, any issuer
     * distinguished name will do.
     * <p>
     * If <code>issuerDN</code> is not <code>null</code>, it should contain a
     * distinguished name, in RFC 2253 format.
     *
     * @param issuerDN a distinguished name in RFC 2253 format
     *                 (or <code>null</code>)
     * @throws IOException if a parsing error occurs (incorrect form for DN)
     */
    public void setIssuer(String issuerDN) throws IOException {
	if (issuerDN == null) {
	    issuer = null;
	} else {
	    issuer = new X500Name(issuerDN).asX500Principal();
	}
    }

    /**
     * Sets the issuer criterion. The specified distinguished name
     * must match the issuer distinguished name in the
     * <code>X509Certificate</code>. If <code>null</code> is specified,
     * the issuer criterion is disabled and any issuer distinguished name will
     * do.
     * <p>
     * If <code>issuerDN</code> is not <code>null</code>, it should contain a
     * single DER encoded distinguished name, as defined in X.501. The ASN.1
     * notation for this structure is as follows.
     * <pre><code>
     * Name ::= CHOICE {
     *   RDNSequence }
     *
     * RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
     *
     * RelativeDistinguishedName ::=
     *   SET SIZE (1 .. MAX) OF AttributeTypeAndValue
     *
     * AttributeTypeAndValue ::= SEQUENCE {
     *   type     AttributeType,
     *   value    AttributeValue }
     *
     * AttributeType ::= OBJECT IDENTIFIER
     *
     * AttributeValue ::= ANY DEFINED BY AttributeType
     * ....
     * DirectoryString ::= CHOICE {
     *       teletexString           TeletexString (SIZE (1..MAX)),
     *       printableString         PrintableString (SIZE (1..MAX)),
     *       universalString         UniversalString (SIZE (1..MAX)),
     *       utf8String              UTF8String (SIZE (1.. MAX)),
     *       bmpString               BMPString (SIZE (1..MAX)) }
     * </code></pre>
     * <p>
     * Note that the byte array specified here is cloned to protect against
     * subsequent modifications.
     *
     * @param issuerDN a byte array containing the distinguished name
     *                 in ASN.1 DER encoded form (or <code>null</code>)
     * @throws IOException if an encoding error occurs (incorrect form for DN)
     */
    public void setIssuer(byte[] issuerDN) throws IOException {
	try {
	    issuer = (issuerDN == null ? null : new X500Principal(issuerDN));
	} catch (IllegalArgumentException e) {
	    throw (IOException)new IOException("Invalid name").initCause(e);
	}
    }
  
    /**
     * Sets the subject criterion. The specified distinguished name
     * must match the subject distinguished name in the
     * <code>X509Certificate</code>. If <code>null</code>, any subject
     * distinguished name will do.
     *
     * @param subject a distinguished name as X500Principal
     *                  (or <code>null</code>)
     * @since 1.5
     */
    public void setSubject(X500Principal subject) {
	this.subject = subject;
    }

    /**
     * <strong>Denigrated</strong>, use {@linkplain #setSubject(X500Principal)} 
     * or {@linkplain #setSubject(byte[])} instead. This method should not be 
     * relied on as it can fail to match some certificates because of a loss of 
     * encoding information in the RFC 2253 String form of some distinguished
     * names.
     * <p>
     * Sets the subject criterion. The specified distinguished name
     * must match the subject distinguished name in the
     * <code>X509Certificate</code>. If <code>null</code>, any subject
     * distinguished name will do.
     * <p>
     * If <code>subjectDN</code> is not <code>null</code>, it should contain a
     * distinguished name, in RFC 2253 format.
     *
     * @param subjectDN a distinguished name in RFC 2253 format
     *                  (or <code>null</code>)
     * @throws IOException if a parsing error occurs (incorrect form for DN)
     */
    public void setSubject(String subjectDN) throws IOException {
	if (subjectDN == null) {
	    subject = null;
	} else {
	    subject = new X500Name(subjectDN).asX500Principal();
	}
    }

    /**
     * Sets the subject criterion. The specified distinguished name
     * must match the subject distinguished name in the
     * <code>X509Certificate</code>. If <code>null</code>, any subject
     * distinguished name will do.
     * <p>
     * If <code>subjectDN</code> is not <code>null</code>, it should contain a
     * single DER encoded distinguished name, as defined in X.501. For the ASN.1
     * notation for this structure, see
     * {@link #setIssuer(byte [] issuerDN) setIssuer(byte [] issuerDN)}.
     *
     * @param subjectDN a byte array containing the distinguished name in
     *                  ASN.1 DER format (or <code>null</code>)
     * @throws IOException if an encoding error occurs (incorrect form for DN)
     */
    public void setSubject(byte[] subjectDN) throws IOException {
	try {
	    subject = (subjectDN == null ? null : new X500Principal(subjectDN));
	} catch (IllegalArgumentException e) {
	    throw (IOException)new IOException("Invalid name").initCause(e);
	}
    }

    /**
     * Sets the subjectKeyIdentifier criterion. The
     * <code>X509Certificate</code> must contain a SubjectKeyIdentifier
     * extension for which the contents of the extension
     * matches the specified criterion value.
     * If the criterion value is <code>null</code>, no
     * subjectKeyIdentifier check will be done.
     * <p>
     * If <code>subjectKeyID</code> is not <code>null</code>, it
     * should contain a single DER encoded value corresponding to the contents
     * of the extension value (not including the object identifier,
     * criticality setting, and encapsulating OCTET STRING)
     * for a SubjectKeyIdentifier extension.
     * The ASN.1 notation for this structure follows.
     * <p>
     * <pre><code>
     * SubjectKeyIdentifier ::= KeyIdentifier
     *
     * KeyIdentifier ::= OCTET STRING
     * </code></pre>
     * <p>
     * Since the format of subject key identifiers is not mandated by
     * any standard, subject key identifiers are not parsed by the
     * <code>X509CertSelector</code>. Instead, the values are compared using
     * a byte-by-byte comparison.
     * <p>
     * Note that the byte array supplied here is cloned to protect against
     * subsequent modifications.
     *
     * @param subjectKeyID the subject key identifier (or <code>null</code>)
     * @see #getSubjectKeyIdentifier
     */
    public void setSubjectKeyIdentifier(byte[] subjectKeyID) {
	if (subjectKeyID == null) {
	    this.subjectKeyID = null;
	} else {
	    this.subjectKeyID = (byte[])subjectKeyID.clone();
	}
    }

    /**
     * Sets the authorityKeyIdentifier criterion. The
     * <code>X509Certificate</code> must contain an
     * AuthorityKeyIdentifier extension for which the contents of the
     * extension value matches the specified criterion value.
     * If the criterion value is <code>null</code>, no
     * authorityKeyIdentifier check will be done.
     * <p>
     * If <code>authorityKeyID</code> is not <code>null</code>, it
     * should contain a single DER encoded value corresponding to the contents
     * of the extension value (not including the object identifier,
     * criticality setting, and encapsulating OCTET STRING)
     * for an AuthorityKeyIdentifier extension.
     * The ASN.1 notation for this structure follows.
     * <p>
     * <pre><code>
     * AuthorityKeyIdentifier ::= SEQUENCE {
     *    keyIdentifier             [0] KeyIdentifier           OPTIONAL,
     *    authorityCertIssuer       [1] GeneralNames            OPTIONAL,
     *    authorityCertSerialNumber [2] CertificateSerialNumber OPTIONAL  }
     *
     * KeyIdentifier ::= OCTET STRING
     * </code></pre>
     * <p>
     * Authority key identifiers are not parsed by the
     * <code>X509CertSelector</code>.  Instead, the values are
     * compared using a byte-by-byte comparison.
     * <p>
     * When the <code>keyIdentifier</code> field of
     * <code>AuthorityKeyIdentifier</code> is populated, the value is
     * usually taken from the <code>SubjectKeyIdentifier</code> extension
     * in the issuer's certificate.  Note, however, that the result of
     * <code>X509Certificate.getExtensionValue(&lt;SubjectKeyIdentifier Object 
     * Identifier&gt;)</code> on the issuer's certificate may NOT be used 
     * directly as the input to <code>setAuthorityKeyIdentifier</code>.
     * This is because the SubjectKeyIdentifier contains
     * only a KeyIdentifier OCTET STRING, and not a SEQUENCE of
     * KeyIdentifier, GeneralNames, and CertificateSerialNumber.
     * In order to use the extension value of the issuer certificate's
     * <code>SubjectKeyIdentifier</code>
     * extension, it will be necessary to extract the value of the embedded
     * <code>KeyIdentifier</code> OCTET STRING, then DER encode this OCTET
     * STRING inside a SEQUENCE.
     * For more details on SubjectKeyIdentifier, see
     * {@link #setSubjectKeyIdentifier(byte[] subjectKeyID)}.
     * <p>
     * Note also that the byte array supplied here is cloned to protect against
     * subsequent modifications.
     * 
     * @param authorityKeyID the authority key identifier 
     *        (or <code>null</code>) 
     * @see #getAuthorityKeyIdentifier
     */
    public void setAuthorityKeyIdentifier(byte[] authorityKeyID) {
	if (authorityKeyID == null) {
	    this.authorityKeyID = null;
	} else {
	    this.authorityKeyID = (byte[])authorityKeyID.clone();
	}
    }

    /**
     * Sets the certificateValid criterion. The specified date must fall
     * within the certificate validity period for the
     * <code>X509Certificate</code>. If <code>null</code>, no certificateValid
     * check will be done.
     * <p>
     * Note that the <code>Date</code> supplied here is cloned to protect 
     * against subsequent modifications.
     *
     * @param certValid the <code>Date</code> to check (or <code>null</code>)
     * @see #getCertificateValid
     */
    public void setCertificateValid(Date certValid) {
	if (certValid == null) {
	    certificateValid = null;
	} else {
	    certificateValid = (Date)certValid.clone();
	}
    }

    /**
     * Sets the privateKeyValid criterion. The specified date must fall
     * within the private key validity period for the
     * <code>X509Certificate</code>. If <code>null</code>, no privateKeyValid
     * check will be done.
     * <p>
     * Note that the <code>Date</code> supplied here is cloned to protect 
     * against subsequent modifications.
     *
     * @param privateKeyValid the <code>Date</code> to check (or
     *                        <code>null</code>)
     * @see #getPrivateKeyValid
     */
    public void setPrivateKeyValid(Date privateKeyValid) {
	if (privateKeyValid == null) {
	    this.privateKeyValid = null;
	} else {
	    this.privateKeyValid = (Date)privateKeyValid.clone();
	}
    }

    /**
     * Sets the subjectPublicKeyAlgID criterion. The
     * <code>X509Certificate</code> must contain a subject public key
     * with the specified algorithm. If <code>null</code>, no
     * subjectPublicKeyAlgID check will be done.
     * 
     * @param oid The object identifier (OID) of the algorithm to check
     *            for (or <code>null</code>). An OID is represented by a
     *            set of nonnegative integers separated by periods.
     * @throws IOException if the OID is invalid, such as
     * the first component being not 0, 1 or 2 or the second component
     * being greater than 39.
     *
     * @see #getSubjectPublicKeyAlgID
     */
    public void setSubjectPublicKeyAlgID(String oid) throws IOException {
	if (oid == null) {
	    subjectPublicKeyAlgID = null;
	} else {
	    subjectPublicKeyAlgID = new ObjectIdentifier(oid);
	}
    }
    
    /**
     * Sets the subjectPublicKey criterion. The
     * <code>X509Certificate</code> must contain the specified subject public
     * key. If <code>null</code>, no subjectPublicKey check will be done.
     * 
     * @param key the subject public key to check for (or <code>null</code>)
     * @see #getSubjectPublicKey
     */
    public void setSubjectPublicKey(PublicKey key) {
	if (key == null) {
	    subjectPublicKey = null;
	    subjectPublicKeyBytes = null;
	} else {
	    subjectPublicKey = key;
	    subjectPublicKeyBytes = key.getEncoded();
	}
    }

    /**
     * Sets the subjectPublicKey criterion. The <code>X509Certificate</code>
     * must contain the specified subject public key. If <code>null</code>,
     * no subjectPublicKey check will be done.
     * <p>
     * Because this method allows the public key to be specified as a byte
     * array, it may be used for unknown key types.
     * <p>
     * If <code>key</code> is not <code>null</code>, it should contain a
     * single DER encoded SubjectPublicKeyInfo structure, as defined in X.509.
     * The ASN.1 notation for this structure is as follows.
     * <pre><code>
     * SubjectPublicKeyInfo  ::=  SEQUENCE  {
     *   algorithm            AlgorithmIdentifier,
     *   subjectPublicKey     BIT STRING  }
     *
     * AlgorithmIdentifier  ::=  SEQUENCE  {
     *   algorithm               OBJECT IDENTIFIER,
     *   parameters              ANY DEFINED BY algorithm OPTIONAL  }
     *                              -- contains a value of the type
     *                              -- registered for use with the
     *                              -- algorithm object identifier value
     * </code></pre>
     * <p>
     * Note that the byte array supplied here is cloned to protect against
     * subsequent modifications.
     * 
     * @param key a byte array containing the subject public key in ASN.1 DER
     *            form (or <code>null</code>)
     * @throws IOException if an encoding error occurs (incorrect form for 
     * subject public key)
     * @see #getSubjectPublicKey
     */
    public void setSubjectPublicKey(byte[] key) throws IOException {
	if (key == null) {
	    subjectPublicKey = null;
	    subjectPublicKeyBytes = null;
	} else {
	    subjectPublicKeyBytes = (byte[])key.clone();
	    subjectPublicKey = X509Key.parse(new DerValue(subjectPublicKeyBytes));
	}
    }

    /**
     * Sets the keyUsage criterion. The <code>X509Certificate</code>
     * must allow the specified keyUsage values. If <code>null</code>, no
     * keyUsage check will be done. Note that an <code>X509Certificate</code> 
     * that has no keyUsage extension implicitly allows all keyUsage values.
     * <p>
     * Note that the boolean array supplied here is cloned to protect against
     * subsequent modifications.
     *
     * @param keyUsage a boolean array in the same format as the boolean
     *                 array returned by
     * {@link X509Certificate#getKeyUsage() X509Certificate.getKeyUsage()}.
     *                 Or <code>null</code>.
     * @see #getKeyUsage
     */
    public void setKeyUsage(boolean[] keyUsage) {
	if (keyUsage == null) {
	    this.keyUsage = null;
	} else {
	    this.keyUsage = (boolean[])keyUsage.clone();
	}
    }

    /**
     * Sets the extendedKeyUsage criterion. The <code>X509Certificate</code>
     * must allow the specified key purposes in its extended key usage
     * extension. If <code>keyPurposeSet</code> is empty or <code>null</code>, 
     * no extendedKeyUsage check will be done. Note that an 
     * <code>X509Certificate</code> that has no extendedKeyUsage extension 
     * implicitly allows all key purposes.
     * <p>
     * Note that the <code>Set</code> is cloned to protect against
     * subsequent modifications.
     *
     * @param keyPurposeSet a <code>Set</code> of key purpose OIDs in string 
     * format (or <code>null</code>). Each OID is represented by a set of 
     * nonnegative integers separated by periods.
     * @throws IOException if the OID is invalid, such as
     * the first component being not 0, 1 or 2 or the second component
     * being greater than 39.
     * @see #getExtendedKeyUsage
     */
    public void setExtendedKeyUsage(Set<String> keyPurposeSet) throws IOException {
	if ((keyPurposeSet == null) || keyPurposeSet.isEmpty()) {
	    this.keyPurposeSet = null;
	    keyPurposeOIDSet = null;
	} else {
	    this.keyPurposeSet = 
		Collections.unmodifiableSet(new HashSet<String>(keyPurposeSet));
	    keyPurposeOIDSet = new HashSet<ObjectIdentifier>();
	    for (String s : this.keyPurposeSet) {
		keyPurposeOIDSet.add(new ObjectIdentifier(s));
	    }
	}
    }

    /**
     * Enables/disables matching all of the subjectAlternativeNames 
     * specified in the {@link #setSubjectAlternativeNames
     * setSubjectAlternativeNames} or {@link #addSubjectAlternativeName
     * addSubjectAlternativeName} methods. If enabled, 
     * the <code>X509Certificate</code> must contain all of the 
     * specified subject alternative names. If disabled, the 
     * <code>X509Certificate</code> must contain at least one of the 
     * specified subject alternative names.
     *
     * <p>The matchAllNames flag is <code>true</code> by default.
     *
     * @param matchAllNames if <code>true</code>, the flag is enabled;
     * if <code>false</code>, the flag is disabled.
     * @see #getMatchAllSubjectAltNames
     */
    public void setMatchAllSubjectAltNames(boolean matchAllNames) {
	this.matchAllSubjectAltNames = matchAllNames;
    } 

    /**
     * Sets the subjectAlternativeNames criterion. The
     * <code>X509Certificate</code> must contain all or at least one of the 
     * specified subjectAlternativeNames, depending on the value of
     * the matchAllNames flag (see {@link #setMatchAllSubjectAltNames
     * setMatchAllSubjectAltNames}).
     * <p>
     * This method allows the caller to specify, with a single method call,
     * the complete set of subject alternative names for the
     * subjectAlternativeNames criterion. The specified value replaces
     * the previous value for the subjectAlternativeNames criterion.
     * <p>
     * The <code>names</code> parameter (if not <code>null</code>) is a
     * <code>Collection</code> with one
     * entry for each name to be included in the subject alternative name
     * criterion. Each entry is a <code>List</code> whose first entry is an
     * <code>Integer</code> (the name type, 0-8) and whose second
     * entry is a <code>String</code> or a byte array (the name, in
     * string or ASN.1 DER encoded form, respectively).
     * There can be multiple names of the same type. If <code>null</code>
     * is supplied as the value for this argument, no
     * subjectAlternativeNames check will be performed.
     * <p>
     * Each subject alternative name in the <code>Collection</code>
     * may be specified either as a <code>String</code> or as an ASN.1 encoded
     * byte array. For more details about the formats used, see
     * {@link #addSubjectAlternativeName(int type, String name) 
     * addSubjectAlternativeName(int type, String name)} and
     * {@link #addSubjectAlternativeName(int type, byte [] name) 
     * addSubjectAlternativeName(int type, byte [] name)}.
     * <p>
     * <strong>Note:</strong> for distinguished names, specify the byte
     * array form instead of the String form. See the note in 
     * {@link #addSubjectAlternativeName(int, String)} for more information. 
     * <p>
     * Note that the <code>names</code> parameter can contain duplicate
     * names (same name and name type), but they may be removed from the
     * <code>Collection</code> of names returned by the
     * {@link #getSubjectAlternativeNames getSubjectAlternativeNames} method.
     * <p>
     * Note that a deep copy is performed on the <code>Collection</code> to
     * protect against subsequent modifications.
     *
     * @param names a <code>Collection</code> of names (or <code>null</code>)
     * @throws IOException if a parsing error occurs
     * @see #getSubjectAlternativeNames
     */
    public void setSubjectAlternativeNames(Collection<List<?>> names) 
	    throws IOException {
	if (names == null) {
	    subjectAlternativeNames = null;
	    subjectAlternativeGeneralNames = null;
	} else {
	    if (names.isEmpty()) {
		subjectAlternativeNames = null;
		subjectAlternativeGeneralNames = null;
		return;
	    }
	    Set<List<?>> tempNames = cloneAndCheckNames(names);
	    // Ensure that we either set both of these or neither
	    subjectAlternativeGeneralNames = parseNames(tempNames);
	    subjectAlternativeNames = tempNames;
	}
    }

    /**
     * Adds a name to the subjectAlternativeNames criterion. The
     * <code>X509Certificate</code> must contain all or at least one
     * of the specified subjectAlternativeNames, depending on the value of
     * the matchAllNames flag (see {@link #setMatchAllSubjectAltNames
     * setMatchAllSubjectAltNames}).
     * <p>
     * This method allows the caller to add a name to the set of subject
     * alternative names. 
     * The specified name is added to any previous value for the
     * subjectAlternativeNames criterion. If the specified name is a 
     * duplicate, it may be ignored.
     * <p>
     * The name is provided in string format. RFC 822, DNS, and URI names
     * use the well-established string formats for those types (subject to
     * the restrictions included in RFC 2459). IPv4 address names are
     * supplied using dotted quad notation. OID address names are represented
     * as a series of nonnegative integers separated by periods. And
     * directory names (distinguished names) are supplied in RFC 2253 format.
     * No standard string format is defined for otherNames, X.400 names,
     * EDI party names, IPv6 address names, or any other type of names. They
     * should be specified using the 
     * {@link #addSubjectAlternativeName(int type, byte [] name) 
     * addSubjectAlternativeName(int type, byte [] name)}
     * method.
     * <p>
     * <strong>Note:</strong> for distinguished names, use 
     * {@linkplain #addSubjectAlternativeName(int, byte[])} instead. 
     * This method should not be relied on as it can fail to match some 
     * certificates because of a loss of encoding information in the RFC 2253 
     * String form of some distinguished names.
     *
     * @param type the name type (0-8, as specified in
     *             RFC 2459, section 4.2.1.7)
     * @param name the name in string form (not <code>null</code>)
     * @throws IOException if a parsing error occurs
     */
    public void addSubjectAlternativeName(int type, String name)
	    throws IOException {
	addSubjectAlternativeNameInternal(type, name);
    }

    /**
     * Adds a name to the subjectAlternativeNames criterion. The
     * <code>X509Certificate</code> must contain all or at least one
     * of the specified subjectAlternativeNames, depending on the value of
     * the matchAllNames flag (see {@link #setMatchAllSubjectAltNames
     * setMatchAllSubjectAltNames}).
     * <p>
     * This method allows the caller to add a name to the set of subject
     * alternative names.
     * The specified name is added to any previous value for the
     * subjectAlternativeNames criterion. If the specified name is a 
     * duplicate, it may be ignored.
     * <p>
     * The name is provided as a byte array. This byte array should contain
     * the DER encoded name, as it would appear in the GeneralName structure
     * defined in RFC 2459 and X.509. The encoded byte array should only contain
     * the encoded value of the name, and should not include the tag associated 
     * with the name in the GeneralName structure. The ASN.1 definition of this 
     * structure appears below.
     * <pre><code>
     *  GeneralName ::= CHOICE {
     *       otherName                       [0]     OtherName,
     *       rfc822Name                      [1]     IA5String,
     *       dNSName                         [2]     IA5String,
     *       x400Address                     [3]     ORAddress,
     *       directoryName                   [4]     Name,
     *       ediPartyName                    [5]     EDIPartyName,
     *       uniformResourceIdentifier       [6]     IA5String,
     *       iPAddress                       [7]     OCTET STRING,
     *       registeredID                    [8]     OBJECT IDENTIFIER}
     * </code></pre>
     * <p>
     * Note that the byte array supplied here is cloned to protect against
     * subsequent modifications.
     * 
     * @param type the name type (0-8, as listed above)
     * @param name a byte array containing the name in ASN.1 DER encoded form
     * @throws IOException if a parsing error occurs
     */
    public void addSubjectAlternativeName(int type, byte[] name)
	    throws IOException {
	// clone because byte arrays are modifiable
	addSubjectAlternativeNameInternal(type, name.clone());
    }

    /**
     * A private method that adds a name (String or byte array) to the
     * subjectAlternativeNames criterion. The <code>X509Certificate</code>
     * must contain the specified subjectAlternativeName.
     *
     * @param type the name type (0-8, as specified in
     *             RFC 2459, section 4.2.1.7)
     * @param name the name in string or byte array form
     * @throws IOException if a parsing error occurs
     */
    private void addSubjectAlternativeNameInternal(int type, Object name)
	    throws IOException {
	// First, ensure that the name parses
	GeneralNameInterface tempName = makeGeneralNameInterface(type, name);
	if (subjectAlternativeNames == null) {
	    subjectAlternativeNames = new HashSet<List<?>>();
	}
	if (subjectAlternativeGeneralNames == null) {
	    subjectAlternativeGeneralNames = new HashSet<GeneralNameInterface>();
	}
	List<Object> list = new ArrayList<Object>(2);
	list.add(Integer.valueOf(type));
	list.add(name);
	subjectAlternativeNames.add(list);
	subjectAlternativeGeneralNames.add(tempName);
    }

    /**
     * Parse an argument of the form passed to setSubjectAlternativeNames,
     * returning a <code>Collection</code> of 
     * <code>GeneralNameInterface</code>s.
     * Throw an IllegalArgumentException or a ClassCastException
     * if the argument is malformed.
     *
     * @param names a Collection with one entry per name.
     *              Each entry is a <code>List</code> whose first entry
     *              is an Integer (the name type, 0-8) and whose second
     *              entry is a String or a byte array (the name, in
     *              string or ASN.1 DER encoded form, respectively).
     *              There can be multiple names of the same type. Null is
     *              not an acceptable value.
     * @return a Set of <code>GeneralNameInterface</code>s
     * @throws IOException if a parsing error occurs
     */
    private static Set<GeneralNameInterface> parseNames(Collection<List<?>> names) throws IOException {
	Set<GeneralNameInterface> genNames = new HashSet<GeneralNameInterface>();
	Iterator<List<?>> i = names.iterator();
	while (i.hasNext()) {
	    Object o = i.next();
	    if (!(o instanceof List)) {
		throw new IOException("expected List");
	    }
	    List<Object> nameList = (List<Object>)o;
	    if (nameList.size() != 2) {
		throw new IOException("name list size not 2");
	    }
	    o =  nameList.get(0);
	    if (!(o instanceof Integer)) {
		throw new IOException("expected an Integer");
	    }
	    int nameType = ((Integer)o).intValue();
	    o = nameList.get(1);
	    genNames.add(makeGeneralNameInterface(nameType, o));
	}
	return genNames;
    }
  
    /**
     * Compare for equality two objects of the form passed to
     * setSubjectAlternativeNames (or X509CRLSelector.setIssuerNames).
     * Throw an <code>IllegalArgumentException</code> or a
     * <code>ClassCastException</code> if one of the objects is malformed.
     *
     * @param object1 a Collection containing the first object to compare
     * @param object2 a Collection containing the second object to compare
     * @return true if the objects are equal, false otherwise
     */
    static boolean equalNames(Collection object1, Collection object2) {
	if ((object1 == null) || (object2 == null)) {
	    return object1 == object2;
	}
	return object1.equals(object2);
    }

    /**
     * Make a <code>GeneralNameInterface</code> out of a name type (0-8) and an
     * Object that may be a byte array holding the ASN.1 DER encoded
     * name or a String form of the name.  Except for X.509
     * Distinguished Names, the String form of the name must not be the
     * result from calling toString on an existing GeneralNameInterface
     * implementing class.  The output of toString is not compatible
     * with the String constructors for names other than Distinguished
     * Names.
     *
     * @param type name type (0-8)
     * @param name name as ASN.1 Der-encoded byte array or String
     * @return a GeneralNameInterface name
     * @throws IOException if a parsing error occurs
     */
    static GeneralNameInterface makeGeneralNameInterface(int type, Object name)
	    throws IOException {
	GeneralNameInterface result;
	if (debug != null) {
	    debug.println("X509CertSelector.makeGeneralNameInterface("
	        + type + ")...");
	}
  
	if (name instanceof String) {
	    if (debug != null) {
		debug.println("X509CertSelector.makeGeneralNameInterface() "
		    + "name is String: " + name);
	    }
	    switch (type) {
	    case NAME_RFC822:
		result = new RFC822Name((String)name);
		break;
	    case NAME_DNS:
		result = new DNSName((String)name);
		break;
	    case NAME_DIRECTORY:
		result = new X500Name((String)name);
		break;
	    case NAME_URI:
		result = new URIName((String)name);
		break;
	    case NAME_IP:
		result = new IPAddressName((String)name);
		break;
	    case NAME_OID:
		result = new OIDName((String)name);
		break;
	    default:
		throw new IOException("unable to parse String names of type "
				      + type);
	    }
	    if (debug != null) {
		debug.println("X509CertSelector.makeGeneralNameInterface() "
		    + "result: " + result.toString());
	    }
	} else if (name instanceof byte[]) {
	    DerValue val = new DerValue((byte[]) name);
	    if (debug != null) {
		debug.println
		    ("X509CertSelector.makeGeneralNameInterface() is byte[]");
	    }
    
	    switch (type) {
	    case NAME_ANY:
		result = new OtherName(val);
		break;
	    case NAME_RFC822:
		result = new RFC822Name(val);
		break;
	    case NAME_DNS:
		result = new DNSName(val);
		break;
	    case NAME_X400:
		result = new X400Address(val);
		break;
	    case NAME_DIRECTORY:
		result = new X500Name(val);
		break;
	    case NAME_EDI:
		result = new EDIPartyName(val);
		break;
	    case NAME_URI:
		result = new URIName(val);
		break;
	    case NAME_IP:
		result = new IPAddressName(val);
		break;
	    case NAME_OID:
		result = new OIDName(val);
		break;
	    default:
		throw new IOException("unable to parse byte array names of "
		    + "type " + type);
	    }
	    if (debug != null) {
		debug.println("X509CertSelector.makeGeneralNameInterface() result: "
		    + result.toString());
	    }
	} else {
	    if (debug != null) {
		debug.println("X509CertSelector.makeGeneralName() input name "
		    + "not String or byte array");
	    }
	    throw new IOException("name not String or byte array");
	}
	return result;
    }


    /**
     * Sets the name constraints criterion. The <code>X509Certificate</code>
     * must have subject and subject alternative names that
     * meet the specified name constraints.
     * <p>
     * The name constraints are specified as a byte array. This byte array
     * should contain the DER encoded form of the name constraints, as they
     * would appear in the NameConstraints structure defined in RFC 2459
     * and X.509. The ASN.1 definition of this structure appears below.
     *
     * <pre><code>
     *  NameConstraints ::= SEQUENCE {
     *       permittedSubtrees       [0]     GeneralSubtrees OPTIONAL,
     *       excludedSubtrees        [1]     GeneralSubtrees OPTIONAL }
     *
     *  GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree
     *
     *  GeneralSubtree ::= SEQUENCE {
     *       base                    GeneralName,
     *       minimum         [0]     BaseDistance DEFAULT 0,
     *       maximum         [1]     BaseDistance OPTIONAL }
     *
     *  BaseDistance ::= INTEGER (0..MAX)
     *
     *  GeneralName ::= CHOICE {
     *       otherName                       [0]     OtherName,
     *       rfc822Name                      [1]     IA5String,
     *       dNSName                         [2]     IA5String,
     *       x400Address                     [3]     ORAddress,
     *       directoryName                   [4]     Name,
     *       ediPartyName                    [5]     EDIPartyName,
     *       uniformResourceIdentifier       [6]     IA5String,
     *       iPAddress                       [7]     OCTET STRING,
     *       registeredID                    [8]     OBJECT IDENTIFIER}
     * </code></pre>
     * <p>
     * Note that the byte array supplied here is cloned to protect against
     * subsequent modifications.
     *
     * @param bytes a byte array containing the ASN.1 DER encoding of
     *              a NameConstraints extension to be used for checking
     *              name constraints. Only the value of the extension is
     *              included, not the OID or criticality flag. Can be
     *              <code>null</code>,
     *              in which case no name constraints check will be performed.
     * @throws IOException if a parsing error occurs
     * @see #getNameConstraints
     */
    public void setNameConstraints(byte[] bytes) throws IOException {
	if (bytes == null) {
	    ncBytes = null;
	    nc = null;
	} else {
	    ncBytes = (byte[])bytes.clone();
	    nc = new NameConstraintsExtension(FALSE, bytes);
	}
    }

    /**
     * Sets the basic constraints constraint. If the value is greater than or
     * equal to zero, <code>X509Certificates</code> must include a 
     * basicConstraints extension with
     * a pathLen of at least this value. If the value is -2, only end-entity
     * certificates are accepted. If the value is -1, no check is done.
     * <p>
     * This constraint is useful when building a certification path forward 
     * (from the target toward the trust anchor. If a partial path has been 
     * built, any candidate certificate must have a maxPathLen value greater 
     * than or equal to the number of certificates in the partial path.
     *
     * @param minMaxPathLen the value for the basic constraints constraint
     * @throws IllegalArgumentException if the value is less than -2
     * @see #getBasicConstraints
     */
    public void setBasicConstraints(int minMaxPathLen) {
	if (minMaxPathLen < -2) {
	    throw new IllegalArgumentException("basic constraints less than -2");
	}
	basicConstraints = minMaxPathLen;
    }

    /**
     * Sets the policy constraint. The <code>X509Certificate</code> must
     * include at least one of the specified policies in its certificate 
     * policies extension. If <code>certPolicySet</code> is empty, then the 
     * <code>X509Certificate</code> must include at least some specified policy
     * in its certificate policies extension. If <code>certPolicySet</code> is
     * <code>null</code>, no policy check will be performed.
     * <p>
     * Note that the <code>Set</code> is cloned to protect against
     * subsequent modifications.
     *
     * @param certPolicySet a <code>Set</code> of certificate policy OIDs in
     *                      string format (or <code>null</code>). Each OID is 
     *                      represented by a set of nonnegative integers 
     *			  separated by periods.
     * @throws IOException if a parsing error occurs on the OID such as
     * the first component is not 0, 1 or 2 or the second component is
     * greater than 39.
     * @see #getPolicy
     */
    public void setPolicy(Set<String> certPolicySet) throws IOException {
	if (certPolicySet == null) {
	    policySet = null;
	    policy = null;
	} else {
	    // Snapshot set and parse it
	    Set<String> tempSet = Collections.unmodifiableSet
	    				(new HashSet<String>(certPolicySet));
	    /* Convert to Vector of ObjectIdentifiers */
	    Iterator i = tempSet.iterator();
	    Vector<CertificatePolicyId> polIdVector = new Vector<CertificatePolicyId>();
	    while (i.hasNext()) {
		Object o = i.next();
		if (!(o instanceof String)) {
		    throw new IOException("non String in certPolicySet");
		}
		polIdVector.add(new CertificatePolicyId(new ObjectIdentifier(
		  (String)o)));
	    }
	    // If everything went OK, make the changes
	    policySet = tempSet;
	    policy = new CertificatePolicySet(polIdVector);
	}
    }

    /**
     * Sets the pathToNames criterion. The <code>X509Certificate</code> must
     * not include name constraints that would prohibit building a
     * path to the specified names.
     * <p>
     * This method allows the caller to specify, with a single method call,
     * the complete set of names which the <code>X509Certificates</code>'s
     * name constraints must permit. The specified value replaces
     * the previous value for the pathToNames criterion.
     * <p>
     * This constraint is useful when building a certification path forward 
     * (from the target toward the trust anchor. If a partial path has been 
     * built, any candidate certificate must not include name constraints that
     * would prohibit building a path to any of the names in the partial path.
     * <p>
     * The <code>names</code> parameter (if not <code>null</code>) is a
     * <code>Collection</code> with one
     * entry for each name to be included in the pathToNames
     * criterion. Each entry is a <code>List</code> whose first entry is an
     * <code>Integer</code> (the name type, 0-8) and whose second
     * entry is a <code>String</code> or a byte array (the name, in
     * string or ASN.1 DER encoded form, respectively).
     * There can be multiple names of the same type. If <code>null</code>
     * is supplied as the value for this argument, no
     * pathToNames check will be performed.
     * <p>
     * Each name in the <code>Collection</code>
     * may be specified either as a <code>String</code> or as an ASN.1 encoded
     * byte array. For more details about the formats used, see
     * {@link #addPathToName(int type, String name) 
     * addPathToName(int type, String name)} and
     * {@link #addPathToName(int type, byte [] name) 
     * addPathToName(int type, byte [] name)}.
     * <p>
     * <strong>Note:</strong> for distinguished names, specify the byte
     * array form instead of the String form. See the note in
     * {@link #addPathToName(int, String)} for more information. 
     * <p>
     * Note that the <code>names</code> parameter can contain duplicate
     * names (same name and name type), but they may be removed from the
     * <code>Collection</code> of names returned by the
     * {@link #getPathToNames getPathToNames} method.
     * <p>
     * Note that a deep copy is performed on the <code>Collection</code> to
     * protect against subsequent modifications.
     *
     * @param names a <code>Collection</code> with one entry per name
     *              (or <code>null</code>)
     * @throws IOException if a parsing error occurs
     * @see #getPathToNames
     */
    public void setPathToNames(Collection<List<?>> names) throws IOException {
	if ((names == null) || names.isEmpty()) {
	    pathToNames = null;
	    pathToGeneralNames = null;
	} else {
	    Set<List<?>> tempNames = cloneAndCheckNames(names);
	    pathToGeneralNames = parseNames(tempNames);
	    // Ensure that we either set both of these or neither
	    pathToNames = tempNames;
	}
    }
  
    // called from CertPathHelper
    void setPathToNamesInternal(Set<GeneralNameInterface> names) {
	// set names to non-null dummy value
	// this breaks getPathToNames()
	pathToNames = Collections.<List<?>>emptySet();
	pathToGeneralNames = names;
    }

    /**
     * Adds a name to the pathToNames criterion. The <code>X509Certificate</code>
     * must not include name constraints that would prohibit building a
     * path to the specified name.
     * <p>
     * This method allows the caller to add a name to the set of names which
     * the <code>X509Certificates</code>'s name constraints must permit.
     * The specified name is added to any previous value for the
     * pathToNames criterion.  If the name is a duplicate, it may be ignored.
     * <p>
     * The name is provided in string format. RFC 822, DNS, and URI names
     * use the well-established string formats for those types (subject to
     * the restrictions included in RFC 2459). IPv4 address names are
     * supplied using dotted quad notation. OID address names are represented
     * as a series of nonnegative integers separated by periods. And
     * directory names (distinguished names) are supplied in RFC 2253 format.
     * No standard string format is defined for otherNames, X.400 names,
     * EDI party names, IPv6 address names, or any other type of names. They
     * should be specified using the 
     * {@link #addPathToName(int type, byte [] name) 
     * addPathToName(int type, byte [] name)} method.
     * <p>
     * <strong>Note:</strong> for distinguished names, use 
     * {@linkplain #addPathToName(int, byte[])} instead. 
     * This method should not be relied on as it can fail to match some 
     * certificates because of a loss of encoding information in the RFC 2253 
     * String form of some distinguished names.
     *
     * @param type the name type (0-8, as specified in
     *             RFC 2459, section 4.2.1.7)
     * @param name the name in string form
     * @throws IOException if a parsing error occurs
     */
    public void addPathToName(int type, String name) throws IOException {
	addPathToNameInternal(type, name);
    }

    /**
     * Adds a name to the pathToNames criterion. The <code>X509Certificate</code>
     * must not include name constraints that would prohibit building a
     * path to the specified name.
     * <p>
     * This method allows the caller to add a name to the set of names which
     * the <code>X509Certificates</code>'s name constraints must permit.
     * The specified name is added to any previous value for the
     * pathToNames criterion. If the name is a duplicate, it may be ignored.
     * <p>
     * The name is provided as a byte array. This byte array should contain
     * the DER encoded name, as it would appear in the GeneralName structure
     * defined in RFC 2459 and X.509. The ASN.1 definition of this structure
     * appears in the documentation for
     * {@link #addSubjectAlternativeName(int type, byte [] name) 
     * addSubjectAlternativeName(int type, byte [] name)}.
     * <p>
     * Note that the byte array supplied here is cloned to protect against
     * subsequent modifications.
     *
     * @param type the name type (0-8, as specified in
     *             RFC 2459, section 4.2.1.7)
     * @param name a byte array containing the name in ASN.1 DER encoded form
     * @throws IOException if a parsing error occurs
     */
    public void addPathToName(int type, byte [] name) throws IOException {
	// clone because byte arrays are modifiable
	addPathToNameInternal(type, name.clone());
    }

    /**
     * A private method that adds a name (String or byte array) to the
     * pathToNames criterion. The <code>X509Certificate</code> must contain
     * the specified pathToName.
     *
     * @param type the name type (0-8, as specified in
     *             RFC 2459, section 4.2.1.7)
     * @param name the name in string or byte array form
     * @throws IOException if an encoding error occurs (incorrect form for DN)
     */
    private void addPathToNameInternal(int type, Object name)
	    throws IOException {
	// First, ensure that the name parses
	GeneralNameInterface tempName = makeGeneralNameInterface(type, name);
	if (pathToGeneralNames == null) {
	    pathToNames = new HashSet<List<?>>();
	    pathToGeneralNames = new HashSet<GeneralNameInterface>();
	}
	List<Object> list = new ArrayList<Object>(2);
	list.add(Integer.valueOf(type));
	list.add(name);
	pathToNames.add(list);
	pathToGeneralNames.add(tempName);
    }

    /**
     * Returns the certificateEquals criterion. The specified
     * <code>X509Certificate</code> must be equal to the 
     * <code>X509Certificate</code> passed to the <code>match</code> method.
     * If <code>null</code>, this check is not applied.
     *
     * @return the <code>X509Certificate</code> to match (or <code>null</code>)
     * @see #setCertificate
     */
    public X509Certificate getCertificate() {
	return x509Cert;
    }

    /**
     * Returns the serialNumber criterion. The specified serial number
     * must match the certificate serial number in the
     * <code>X509Certificate</code>. If <code>null</code>, any certificate
     * serial number will do.
     *
     * @return the certificate serial number to match
     *                (or <code>null</code>)
     * @see #setSerialNumber
     */
    public BigInteger getSerialNumber() {
	return serialNumber;
    }
  
    /**
     * Returns the issuer criterion as an <code>X500Principal</code>. This
     * distinguished name must match the issuer distinguished name in the
     * <code>X509Certificate</code>. If <code>null</code>, the issuer criterion
     * is disabled and any issuer distinguished name will do.
     *
     * @return the required issuer distinguished name as X500Principal
     *         (or <code>null</code>)
     * @since 1.5
     */
    public X500Principal getIssuer() {
	return issuer;
    }

    /**
     * <strong>Denigrated</strong>, use {@linkplain #getIssuer()} or 
     * {@linkplain #getIssuerAsBytes()} instead. This method should not be 
     * relied on as it can fail to match some certificates because of a loss of 
     * encoding information in the RFC 2253 String form of some distinguished
     * names.
     * <p>
     * Returns the issuer criterion as a <code>String</code>. This
     * distinguished name must match the issuer distinguished name in the
     * <code>X509Certificate</code>. If <code>null</code>, the issuer criterion
     * is disabled and any issuer distinguished name will do.
     * <p>
     * If the value returned is not <code>null</code>, it is a
     * distinguished name, in RFC 2253 format.
     *
     * @return the required issuer distinguished name in RFC 2253 format
     *         (or <code>null</code>)
     */
    public String getIssuerAsString() {
	return (issuer == null ? null : issuer.getName());
    }

    /**
     * Returns the issuer criterion as a byte array. This distinguished name
     * must match the issuer distinguished name in the
     * <code>X509Certificate</code>. If <code>null</code>, the issuer criterion
     * is disabled and any issuer distinguished name will do.
     * <p>
     * If the value returned is not <code>null</code>, it is a byte
     * array containing a single DER encoded distinguished name, as defined in
     * X.501. The ASN.1 notation for this structure is supplied in the
     * documentation for
     * {@link #setIssuer(byte [] issuerDN) setIssuer(byte [] issuerDN)}.
     * <p>
     * Note that the byte array returned is cloned to protect against
     * subsequent modifications.
     *
     * @return a byte array containing the required issuer distinguished name
     *         in ASN.1 DER format (or <code>null</code>)
     * @throws IOException if an encoding error occurs
     */
    public byte[] getIssuerAsBytes() throws IOException {
	return (issuer == null ? null: issuer.getEncoded()); 
    }
  
    /**
     * Returns the subject criterion as an <code>X500Principal</code>. This
     * distinguished name must match the subject distinguished name in the
     * <code>X509Certificate</code>. If <code>null</code>, the subject criterion
     * is disabled and any subject distinguished name will do.
     *
     * @return the required subject distinguished name as X500Principal
     *         (or <code>null</code>)
     * @since 1.5
     */
    public X500Principal getSubject() {
	return subject;
    }

    /**
     * <strong>Denigrated</strong>, use {@linkplain #getSubject()} or 
     * {@linkplain #getSubjectAsBytes()} instead. This method should not be 
     * relied on as it can fail to match some certificates because of a loss of 
     * encoding information in the RFC 2253 String form of some distinguished
     * names.
     * <p>
     * Returns the subject criterion as a <code>String</code>. This
     * distinguished name must match the subject distinguished name in the
     * <code>X509Certificate</code>. If <code>null</code>, the subject criterion
     * is disabled and any subject distinguished name will do.
     * <p>
     * If the value returned is not <code>null</code>, it is a
     * distinguished name, in RFC 2253 format.
     *
     * @return the required subject distinguished name in RFC 2253 format
     *         (or <code>null</code>)
     */
    public String getSubjectAsString() {
	return (subject == null ? null : subject.getName());
    }

    /**
     * Returns the subject criterion as a byte array. This distinguished name
     * must match the subject distinguished name in the
     * <code>X509Certificate</code>. If <code>null</code>, the subject criterion
     * is disabled and any subject distinguished name will do.
     * <p>
     * If the value returned is not <code>null</code>, it is a byte
     * array containing a single DER encoded distinguished name, as defined in
     * X.501. The ASN.1 notation for this structure is supplied in the
     * documentation for
     * {@link #setSubject(byte [] subjectDN) setSubject(byte [] subjectDN)}.
     * <p>
     * Note that the byte array returned is cloned to protect against
     * subsequent modifications.
     *
     * @return a byte array containing the required subject distinguished name
     *         in ASN.1 DER format (or <code>null</code>)
     * @throws IOException if an encoding error occurs
     */
    public byte[] getSubjectAsBytes() throws IOException {
	return (subject == null ? null : subject.getEncoded());
    }

    /**
     * Returns the subjectKeyIdentifier criterion. The
     * <code>X509Certificate</code> must contain a SubjectKeyIdentifier
     * extension with the specified value. If <code>null</code>, no
     * subjectKeyIdentifier check will be done.
     * <p>
     * Note that the byte array returned is cloned to protect against
     * subsequent modifications.
     *
     * @return the key identifier (or <code>null</code>)
     * @see #setSubjectKeyIdentifier
     */
    public byte[] getSubjectKeyIdentifier() {
	if (subjectKeyID == null) {
	    return null;
	}
	return (byte[])subjectKeyID.clone();
    }

    /**
     * Returns the authorityKeyIdentifier criterion. The
     * <code>X509Certificate</code> must contain a AuthorityKeyIdentifier
     * extension with the specified value. If <code>null</code>, no
     * authorityKeyIdentifier check will be done.
     * <p>
     * Note that the byte array returned is cloned to protect against
     * subsequent modifications.
     *
     * @return the key identifier (or <code>null</code>)
     * @see #setAuthorityKeyIdentifier
     */
    public byte[] getAuthorityKeyIdentifier() {
	if (authorityKeyID == null) {
	  return null;
	}
	return (byte[])authorityKeyID.clone();
    }

    /**
     * Returns the certificateValid criterion. The specified date must fall
     * within the certificate validity period for the
     * <code>X509Certificate</code>. If <code>null</code>, no certificateValid
     * check will be done.
     * <p>
     * Note that the <code>Date</code> returned is cloned to protect against
     * subsequent modifications.
     *
     * @return the <code>Date</code> to check (or <code>null</code>)
     * @see #setCertificateValid
     */
    public Date getCertificateValid() {
	if (certificateValid == null) {
	    return null;
	}
	return (Date)certificateValid.clone();
    }

    /**
     * Returns the privateKeyValid criterion. The specified date must fall
     * within the private key validity period for the
     * <code>X509Certificate</code>. If <code>null</code>, no privateKeyValid
     * check will be done.
     * <p>
     * Note that the <code>Date</code> returned is cloned to protect against
     * subsequent modifications.
     *
     * @return the <code>Date</code> to check (or <code>null</code>)
     * @see #setPrivateKeyValid
     */
    public Date getPrivateKeyValid() {
	if (privateKeyValid == null) {
	    return null;
	}
	return (Date)privateKeyValid.clone();
    }

    /**
     * Returns the subjectPublicKeyAlgID criterion. The
     * <code>X509Certificate</code> must contain a subject public key
     * with the specified algorithm. If <code>null</code>, no
     * subjectPublicKeyAlgID check will be done.
     * 
     * @return the object identifier (OID) of the signature algorithm to check
     *         for (or <code>null</code>). An OID is represented by a set of
     *         nonnegative integers separated by periods.
     * @see #setSubjectPublicKeyAlgID
     */
    public String getSubjectPublicKeyAlgID() {
	if (subjectPublicKeyAlgID == null) {
	    return null;
	}
	return subjectPublicKeyAlgID.toString();
    }

    /**
     * Returns the subjectPublicKey criterion. The
     * <code>X509Certificate</code> must contain the specified subject
     * public key. If <code>null</code>, no subjectPublicKey check will be done.
     * 
     * @return the subject public key to check for (or <code>null</code>)
     * @see #setSubjectPublicKey
     */
    public PublicKey getSubjectPublicKey() {
	return subjectPublicKey;
    }

    /**
     * Returns the keyUsage criterion. The <code>X509Certificate</code>
     * must allow the specified keyUsage values. If null, no keyUsage
     * check will be done.
     * <p>
     * Note that the boolean array returned is cloned to protect against
     * subsequent modifications.
     *
     * @return a boolean array in the same format as the boolean
     *                 array returned by
     * {@link X509Certificate#getKeyUsage() X509Certificate.getKeyUsage()}.
     *                 Or <code>null</code>.
     * @see #setKeyUsage
     */
    public boolean[] getKeyUsage() {
	if (keyUsage == null) {
	    return null;
	}
	return (boolean[])keyUsage.clone();
    }

    /**
     * Returns the extendedKeyUsage criterion. The <code>X509Certificate</code>
     * must allow the specified key purposes in its extended key usage
     * extension. If the <code>keyPurposeSet</code> returned is empty or
     * <code>null</code>, no extendedKeyUsage check will be done. Note that an 
     * <code>X509Certificate</code> that has no extendedKeyUsage extension 
     * implicitly allows all key purposes.
     * 
     * @return an immutable <code>Set</code> of key purpose OIDs in string
     * format (or <code>null</code>)
     * @see #setExtendedKeyUsage
     */
    public Set<String> getExtendedKeyUsage() {
	return keyPurposeSet;
    }

    /**
     * Indicates if the <code>X509Certificate</code> must contain all
     * or at least one of the subjectAlternativeNames 
     * specified in the {@link #setSubjectAlternativeNames
     * setSubjectAlternativeNames} or {@link #addSubjectAlternativeName
     * addSubjectAlternativeName} methods. If <code>true</code>, 
     * the <code>X509Certificate</code> must contain all of the 
     * specified subject alternative names. If <code>false</code>, the 
     * <code>X509Certificate</code> must contain at least one of the 
     * specified subject alternative names.
     *
     * @return <code>true</code> if the flag is enabled;
     * <code>false</code> if the flag is disabled. The flag is
     * <code>true</code> by default.
     * @see #setMatchAllSubjectAltNames
     */
    public boolean getMatchAllSubjectAltNames() {
	return matchAllSubjectAltNames;
    }

    /**
     * Returns a copy of the subjectAlternativeNames criterion.
     * The <code>X509Certificate</code> must contain all or at least one
     * of the specified subjectAlternativeNames, depending on the value
     * of the matchAllNames flag (see {@link #getMatchAllSubjectAltNames
     * getMatchAllSubjectAltNames}). If the value returned is 
     * <code>null</code>, no subjectAlternativeNames check will be performed.
     * <p>
     * If the value returned is not <code>null</code>, it is a
     * <code>Collection</code> with
     * one entry for each name to be included in the subject alternative name
     * criterion. Each entry is a <code>List</code> whose first entry is an
     * <code>Integer</code> (the name type, 0-8) and whose second
     * entry is a <code>String</code> or a byte array (the name, in
     * string or ASN.1 DER encoded form, respectively).
     * There can be multiple names of the same type.  Note that the
     * <code>Collection</code> returned may contain duplicate names (same name
     * and name type).
     * <p>
     * Each subject alternative name in the <code>Collection</code>
     * may be specified either as a <code>String</code> or as an ASN.1 encoded
     * byte array. For more details about the formats used, see
     * {@link #addSubjectAlternativeName(int type, String name) 
     * addSubjectAlternativeName(int type, String name)} and
     * {@link #addSubjectAlternativeName(int type, byte [] name) 
     * addSubjectAlternativeName(int type, byte [] name)}.
     * <p>
     * Note that a deep copy is performed on the <code>Collection</code> to
     * protect against subsequent modifications.
     *
     * @return a <code>Collection</code> of names (or <code>null</code>)
     * @see #setSubjectAlternativeNames
     */
    public Collection<List<?>> getSubjectAlternativeNames() {
	if (subjectAlternativeNames == null) {
	    return null;
	}
	return cloneNames(subjectAlternativeNames);
    }

    /**
     * Clone an object of the form passed to
     * setSubjectAlternativeNames and setPathToNames.
     * Throw a <code>RuntimeException</code> if the argument is malformed.
     * <p>
     * This method wraps cloneAndCheckNames, changing any
     * <code>IOException</code> into a <code>RuntimeException</code>. This
     * method should be used when the object being
     * cloned has already been checked, so there should never be any exceptions.
     *
     * @param names a <code>Collection</code> with one entry per name.
     *              Each entry is a <code>List</code> whose first entry
     *              is an Integer (the name type, 0-8) and whose second
     *              entry is a String or a byte array (the name, in
     *              string or ASN.1 DER encoded form, respectively).
     *              There can be multiple names of the same type. Null
     *              is not an acceptable value.
     * @return a deep copy of the specified <code>Collection</code>
     * @throws RuntimeException if a parsing error occurs
     */
    private static Set<List<?>> cloneNames(Collection<List<?>> names) {
	try {
	    return cloneAndCheckNames(names);
	} catch (IOException e) {
	    throw new RuntimeException("cloneNames encountered IOException: " +
				       e.getMessage());
	}
    }

    /**
     * Clone and check an argument of the form passed to
     * setSubjectAlternativeNames and setPathToNames.
     * Throw an <code>IOException</code> if the argument is malformed.
     *
     * @param names a <code>Collection</code> with one entry per name.
     *              Each entry is a <code>List</code> whose first entry
     *              is an Integer (the name type, 0-8) and whose second
     *              entry is a String or a byte array (the name, in
     *              string or ASN.1 DER encoded form, respectively).
     *              There can be multiple names of the same type.
     *              <code>null</code> is not an acceptable value.
     * @return a deep copy of the specified <code>Collection</code>
     * @throws IOException if a parsing error occurs
     */
    private static Set<List<?>> cloneAndCheckNames(Collection<List<?>> names) throws IOException {
	// Copy the Lists and Collection
	Set<List<?>> namesCopy = new HashSet<List<?>>();
	Iterator<List<?>> i = names.iterator();
	while (i.hasNext()) {
	    Object o = i.next();
	    if (!(o instanceof List)) {
		throw new IOException("expected a List");
	    }
	    namesCopy.add(new ArrayList<Object>((List<?>)o));
	}
    
	// Check the contents of the Lists and clone any byte arrays
	i = namesCopy.iterator();
	while (i.hasNext()) {
	    List<Object> nameList = (List<Object>)i.next();
	    if (nameList.size() != 2) {
		throw new IOException("name list size not 2");
	    }
	    Object o = nameList.get(0);
	    if (!(o instanceof Integer)) {
		throw new IOException("expected an Integer");
	    }
	    int nameType = ((Integer) o).intValue();
	    if ((nameType < 0) || (nameType > 8)) {
		throw new IOException("name type not 0-8");
	    }
	    Object nameObject = nameList.get(1);
	    if (!(nameObject instanceof byte[]) &&
		!(nameObject instanceof String)) {
		if (debug != null) {
		    debug.println("X509CertSelector.cloneAndCheckNames() "
		        + "name not byte array");
		}
		throw new IOException("name not byte array or String");
	    }
	    if (nameObject instanceof byte[]) {
		nameList.set(1, ((byte[]) nameObject).clone());
	    }
	}
	return namesCopy;
    }

    /**
     * Returns the name constraints criterion. The <code>X509Certificate</code>
     * must have subject and subject alternative names that
     * meet the specified name constraints.
     * <p>
     * The name constraints are returned as a byte array. This byte array
     * contains the DER encoded form of the name constraints, as they
     * would appear in the NameConstraints structure defined in RFC 2459
     * and X.509. The ASN.1 notation for this structure is supplied in the
     * documentation for
     * {@link #setNameConstraints(byte [] bytes) setNameConstraints(byte [] bytes)}.
     * <p>
     * Note that the byte array returned is cloned to protect against
     * subsequent modifications.
     *
     * @return a byte array containing the ASN.1 DER encoding of
     *         a NameConstraints extension used for checking name constraints.
     *         <code>null</code> if no name constraints check will be performed.
     * @see #setNameConstraints
     */
    public byte[] getNameConstraints() {
	if (ncBytes == null) {
	    return null;
	} else {
	    return (byte[]) ncBytes.clone();
	}
    }

    /**
     * Returns the basic constraints constraint. If the value is greater than
     * or equal to zero, the <code>X509Certificates</code> must include a
     * basicConstraints extension with a pathLen of at least this value.
     * If the value is -2, only end-entity certificates are accepted. If
     * the value is -1, no basicConstraints check is done.
     *
     * @return the value for the basic constraints constraint
     * @see #setBasicConstraints
     */
    public int getBasicConstraints() {
	return basicConstraints;
    }

    /**
     * Returns the policy criterion. The <code>X509Certificate</code> must
     * include at least one of the specified policies in its certificate policies
     * extension. If the <code>Set</code> returned is empty, then the 
     * <code>X509Certificate</code> must include at least some specified policy
     * in its certificate policies extension. If the <code>Set</code> returned is
     * <code>null</code>, no policy check will be performed.
     *
     * @return an immutable <code>Set</code> of certificate policy OIDs in
     *         string format (or <code>null</code>)
     * @see #setPolicy
     */
    public Set<String> getPolicy() {
	return policySet;
    }

    /**
     * Returns a copy of the pathToNames criterion. The
     * <code>X509Certificate</code> must not include name constraints that would
     * prohibit building a path to the specified names. If the value
     * returned is <code>null</code>, no pathToNames check will be performed.
     * <p>
     * If the value returned is not <code>null</code>, it is a
     * <code>Collection</code> with one
     * entry for each name to be included in the pathToNames
     * criterion. Each entry is a <code>List</code> whose first entry is an
     * <code>Integer</code> (the name type, 0-8) and whose second
     * entry is a <code>String</code> or a byte array (the name, in
     * string or ASN.1 DER encoded form, respectively).
     * There can be multiple names of the same type. Note that the
     * <code>Collection</code> returned may contain duplicate names (same
     * name and name type).
     * <p>
     * Each name in the <code>Collection</code>
     * may be specified either as a <code>String</code> or as an ASN.1 encoded
     * byte array. For more details about the formats used, see
     * {@link #addPathToName(int type, String name) 
     * addPathToName(int type, String name)} and
     * {@link #addPathToName(int type, byte [] name) 
     * addPathToName(int type, byte [] name)}.
     * <p>
     * Note that a deep copy is performed on the <code>Collection</code> to
     * protect against subsequent modifications.
     *
     * @return a <code>Collection</code> of names (or <code>null</code>)
     * @see #setPathToNames
     */
    public Collection<List<?>> getPathToNames() {
	if (pathToNames == null) {
	    return null;
	}
	return cloneNames(pathToNames);
    }

    /**
     * Return a printable representation of the <code>CertSelector</code>.
     *
     * @return a <code>String</code> describing the contents of the
     *         <code>CertSelector</code>
     */
    public String toString() {
	StringBuffer sb = new StringBuffer();
	sb.append("X509CertSelector: [\n");
	if (x509Cert != null) {
	    sb.append("  Certificate: " + x509Cert.toString() + "\n");
	}
	if (serialNumber != null) {
	    sb.append("  Serial Number: " + serialNumber.toString() + "\n");
	}
	if (issuer != null) {
	    sb.append("  Issuer: " + getIssuerAsString() + "\n");
	}
	if (subject != null) {
	    sb.append("  Subject: " + getSubjectAsString() + "\n");
	}
	sb.append("  matchAllSubjectAltNames flag: " 
		  + String.valueOf(matchAllSubjectAltNames) + "\n");
	if (subjectAlternativeNames != null) {
	    sb.append("  SubjectAlternativeNames:\n");
	    Iterator i = subjectAlternativeNames.iterator();
	    while (i.hasNext()) {
		List list = (List) i.next();
		sb.append("    type " + list.get(0) +
			  ", name " + list.get(1) + "\n");
	    }
	}
	if (subjectKeyID != null) {
	    HexDumpEncoder enc = new HexDumpEncoder();
	    sb.append("  Subject Key Identifier: " + 
		      enc.encodeBuffer(subjectKeyID) + "\n");
	}
	if (authorityKeyID != null) {
	    HexDumpEncoder enc = new HexDumpEncoder();
	    sb.append("  Authority Key Identifier: " + 
		      enc.encodeBuffer(authorityKeyID) + "\n");
	}
	if (certificateValid != null) {
	    sb.append("  Certificate Valid: " + 
		      certificateValid.toString() + "\n");
	}
	if (privateKeyValid != null) {
	    sb.append("  Private Key Valid: " + 
		      privateKeyValid.toString() + "\n");
	}
	if (subjectPublicKeyAlgID != null) {
	    sb.append("  Subject Public Key AlgID: " + 
		      subjectPublicKeyAlgID.toString() + "\n");
	}
	if (subjectPublicKey != null) {
	    sb.append("  Subject Public Key: " + 
		      subjectPublicKey.toString() + "\n");
	}
	if (keyUsage != null) {
	    sb.append("  Key Usage: " + keyUsageToString(keyUsage) + "\n");
	}
	if (keyPurposeSet != null) {
	    sb.append("  Extended Key Usage: " + 
		      keyPurposeSet.toString() + "\n");
	}
	if (policy != null) {
	    sb.append("  Policy: " + policy.toString() + "\n");
	}
	if (pathToGeneralNames != null) {
	    sb.append("  Path to names:\n");
	    Iterator i = pathToGeneralNames.iterator();
	    while (i.hasNext()) {
		sb.append("    " + i.next() + "\n");
	    }
	}
	sb.append("]");
	return sb.toString();
    }

    // Copied from sun.security.x509.KeyUsageExtension
    // (without calling the superclass)
    /**
     * Returns a printable representation of the KeyUsage.
     */
    private static String keyUsageToString(boolean[] k) {
	String s = "KeyUsage [\n";
	try {
	    if (k[0]) {
		s += "  DigitalSignature\n";
	    }
	    if (k[1]) {
		s += "  Non_repudiation\n";
	    }
	    if (k[2]) {
		s += "  Key_Encipherment\n";
	    }
	    if (k[3]) {
		s += "  Data_Encipherment\n";
	    }
	    if (k[4]) {
		s += "  Key_Agreement\n";
	    }
	    if (k[5]) {
		s += "  Key_CertSign\n";
	    }
	    if (k[6]) {
		s += "  Crl_Sign\n";
	    }
	    if (k[7]) {
		s += "  Encipher_Only\n";
	    }
	    if (k[8]) {
		s += "  Decipher_Only\n";
	    }
	} catch (ArrayIndexOutOfBoundsException ex) {}
    
	s += "]\n";
    
	return (s);
    }

    /**
     * Returns an Extension object given any X509Certificate and extension oid.
     * Throw an <code>IOException</code> if the extension byte value is 
     * malformed.
     *
     * @param cert a <code>X509Certificate</code> 
     * @param extId an <code>integer</code> which specifies the extension index. 
     * Currently, the supported extensions are as follows:
     * index 0 - PrivateKeyUsageExtension
     * index 1 - SubjectAlternativeNameExtension
     * index 2 - NameConstraintsExtension   
     * index 3 - CertificatePoliciesExtension
     * index 4 - ExtendedKeyUsageExtension
     * @return an <code>Extension</code> object whose real type is as specified
     * by the extension oid.
     * @throws IOException if cannot construct the <code>Extension</code> 
     * object with the extension encoding retrieved from the passed in 
     * <code>X509Certificate</code>.
     */
    private static Extension getExtensionObject(X509Certificate cert, int extId)
	    throws IOException {
	if (cert instanceof X509CertImpl) {
	    X509CertImpl impl = (X509CertImpl)cert;
	    switch (extId) {
	    case PRIVATE_KEY_USAGE_ID:
		return impl.getPrivateKeyUsageExtension();
	    case SUBJECT_ALT_NAME_ID:
		return impl.getSubjectAlternativeNameExtension();
	    case NAME_CONSTRAINTS_ID:
		return impl.getNameConstraintsExtension();
	    case CERT_POLICIES_ID:
		return impl.getCertificatePoliciesExtension();
	    case EXTENDED_KEY_USAGE_ID:
		return impl.getExtendedKeyUsageExtension();
	    default:
		return null;
	    }
	}
	byte[] rawExtVal = cert.getExtensionValue(EXTENSION_OIDS[extId]);
	if (rawExtVal == null) {
	    return null;
	}
	DerInputStream in = new DerInputStream(rawExtVal);
	byte[] encoded = in.getOctetString();
	switch (extId) {
	case PRIVATE_KEY_USAGE_ID:
	    try {
		return new PrivateKeyUsageExtension(FALSE, encoded);
	    } catch (CertificateException ex) {
		throw new IOException(ex.getMessage());
	    }
	case SUBJECT_ALT_NAME_ID:
	    return new SubjectAlternativeNameExtension(FALSE, encoded);
	case NAME_CONSTRAINTS_ID:
	    return new NameConstraintsExtension(FALSE, encoded);
	case CERT_POLICIES_ID:
	    return new CertificatePoliciesExtension(FALSE, encoded);
	case EXTENDED_KEY_USAGE_ID:
	    return new ExtendedKeyUsageExtension(FALSE, encoded);
	default:
	    return null;
	}
    }

    /**
     * Decides whether a <code>Certificate</code> should be selected.
     *
     * @param cert the <code>Certificate</code> to be checked
     * @return <code>true</code> if the <code>Certificate</code> should be
     *         selected, <code>false</code> otherwise
     */
    public boolean match(Certificate cert) {
	if (!(cert instanceof X509Certificate)) {
	    return false;
	}
	X509Certificate xcert = (X509Certificate)cert;
    
	if (debug != null) {
	    debug.println("X509CertSelector.match(SN: " 
		+ (xcert.getSerialNumber()).toString(16) + "\n  Issuer: " 
		+ xcert.getIssuerDN() + "\n  Subject: " + xcert.getSubjectDN() 
		+ ")");
	}
    
	/* match on X509Certificate */
	if (x509Cert != null) {
	    if (!x509Cert.equals(xcert)) {
		if (debug != null) {
		    debug.println("X509CertSelector.match: "
		    	+ "certs don't match");
		}
		return false;
	    }
	}
	
	/* match on serial number */
	if (serialNumber != null) {
	    if (!serialNumber.equals(xcert.getSerialNumber())) {
		if (debug != null) {
		    debug.println("X509CertSelector.match: "
		    	+ "serial numbers don't match");
		}
		return false;
	    }
	}
    
	/* match on issuer name */
	if (issuer != null) {
	    if (!issuer.equals(xcert.getIssuerX500Principal())) {
		if (debug != null) {
		    debug.println("X509CertSelector.match: "
		    	+ "issuer DNs don't match");
		}
		return false;
	    }
	}
    
	/* match on subject name */
	if (subject != null) {
	    if (!subject.equals(xcert.getSubjectX500Principal())) {
		if (debug != null) {
		    debug.println("X509CertSelector.match: "
		    	+ "subject DNs don't match");
		}
		return false;
	    }
	}
    
	/* match on certificate validity range */
	if (certificateValid != null) {
	    try {
		xcert.checkValidity(certificateValid);
	    } catch (CertificateException e) {
		if (debug != null) {
		    debug.println("X509CertSelector.match: "
		    	+ "certificate not within validity period");
		}
		return false;
	    }
	}
    
	/* match on subject public key */
	if (subjectPublicKeyBytes != null) {
	    byte[] certKey = xcert.getPublicKey().getEncoded();
	    if (!Arrays.equals(subjectPublicKeyBytes, certKey)) {
		if (debug != null) {
		    debug.println("X509CertSelector.match: "
		    	+ "subject public keys don't match");
		}
		return false;
	    }
	}
	
	boolean result = matchBasicConstraints(xcert)
		      && matchKeyUsage(xcert)
		      && matchExtendedKeyUsage(xcert)
		      && matchSubjectKeyID(xcert)
		      && matchAuthorityKeyID(xcert)
		      && matchPrivateKeyValid(xcert)
		      && matchSubjectPublicKeyAlgID(xcert)
		      && matchPolicy(xcert)
		      && matchSubjectAlternativeNames(xcert)
		      && matchPathToNames(xcert)
		      && matchNameConstraints(xcert);
    
	if (result && (debug != null)) {
	    debug.println("X509CertSelector.match returning: true");
	}
	return result;
    }

    /* match on subject key identifier extension value */
    private boolean matchSubjectKeyID(X509Certificate xcert) {
	if (subjectKeyID == null) {
	    return true;
	}
	try {
	    byte[] extVal = xcert.getExtensionValue("2.5.29.14");
	    if (extVal == null) {
		if (debug != null) {
		    debug.println("X509CertSelector.match: "
		    	+ "no subject key ID extension");
		}
		return false;
	    }
	    DerInputStream in = new DerInputStream(extVal);
	    byte[] certSubjectKeyID = in.getOctetString();
	    if (certSubjectKeyID == null || 
	            !Arrays.equals(subjectKeyID, certSubjectKeyID)) {
		if (debug != null) {
		    debug.println("X509CertSelector.match: "
		    	+ "subject key IDs don't match");
		}
		return false;
	    }
	} catch (IOException ex) {
	    if (debug != null) {
		debug.println("X509CertSelector.match: "
		    + "exception in subject key ID check");
	    }
	    return false;
	}
	return true;
    }

    /* match on authority key identifier extension value */
    private boolean matchAuthorityKeyID(X509Certificate xcert) {
	if (authorityKeyID == null) {
	    return true;
	}
	try {
	    byte[] extVal = xcert.getExtensionValue("2.5.29.35");
	    if (extVal == null) {
		if (debug != null) {
		    debug.println("X509CertSelector.match: "
		    	+ "no authority key ID extension");
		}
		return false;
	    }
	    DerInputStream in = new DerInputStream(extVal);
	    byte[] certAuthKeyID = in.getOctetString();
	    if (certAuthKeyID == null || 
	    	    !Arrays.equals(authorityKeyID, certAuthKeyID)) {
		if (debug != null) {
		    debug.println("X509CertSelector.match: "
		    	+ "authority key IDs don't match");
		}
		return false;
	    }
	} catch (IOException ex) {
	    if (debug != null) {
		debug.println("X509CertSelector.match: "
		    + "exception in authority key ID check");
	    }
	    return false;
	}
	return true;
    }

    /* match on private key usage range */
    private boolean matchPrivateKeyValid(X509Certificate xcert) {
	if (privateKeyValid == null) {
	    return true;
	}
	PrivateKeyUsageExtension ext = null;
	try {
	    ext = (PrivateKeyUsageExtension) 
		getExtensionObject(xcert, PRIVATE_KEY_USAGE_ID);
	    if (ext != null) {
	        ext.valid(privateKeyValid);
	    }
	} catch (CertificateExpiredException e1) {
	    if (debug != null) {
	        String time = "n/a";
		try {
		    Date notAfter = 
		    	(Date)ext.get(PrivateKeyUsageExtension.NOT_AFTER);
		    time = notAfter.toString();
		} catch (CertificateException ex) {
		    // not able to retrieve notAfter value
		}
		debug.println("X509CertSelector.match: private key usage not "
		    + "within validity date; ext.NOT_After: " 
		    + time + "; X509CertSelector: " 
		    + this.toString());
		e1.printStackTrace();
	    }
	    return false;
	} catch (CertificateNotYetValidException e2) {
	    if (debug != null) {
		String time = "n/a";
		try {
		    Date notBefore = (Date) 
			ext.get(PrivateKeyUsageExtension.NOT_BEFORE);
		    time = notBefore.toString();
		} catch (CertificateException ex) {
		    // not able to retrieve notBefore value
		}
		debug.println("X509CertSelector.match: private key usage not "
		    + "within validity date; ext.NOT_BEFORE: " 
		    + time + "; X509CertSelector: " 
		    + this.toString());
		e2.printStackTrace();
	    }
	    return false;
	} catch (CertificateException e3) {
	    if (debug != null) {
	        debug.println("X509CertSelector.match: CertificateException "
		    + "in private key usage check; X509CertSelector: " 
		    + this.toString());
	        e3.printStackTrace();
	    }
	    return false;
	} catch (IOException e4) {
	    if (debug != null) {
	        debug.println("X509CertSelector.match: IOException in "
		    + "private key usage check; X509CertSelector: " 
		    + this.toString());
	        e4.printStackTrace();
	    }
	    return false;
	}
	return true;
    }

    /* match on subject public key algorithm OID */
    private boolean matchSubjectPublicKeyAlgID(X509Certificate xcert) {
	if (subjectPublicKeyAlgID == null) {
	    return true;
	}
	try {
	    byte[] encodedKey = xcert.getPublicKey().getEncoded();
	    DerValue val = new DerValue(encodedKey);
	    if (val.tag != DerValue.tag_Sequence) {
		throw new IOException("invalid key format");
	    }
  
	    AlgorithmId algID = AlgorithmId.parse(val.data.getDerValue());
	    if (debug != null) {
		debug.println("X509CertSelector.match: subjectPublicKeyAlgID = " 
		    + subjectPublicKeyAlgID + ", xcert subjectPublicKeyAlgID = " 
		    + algID.getOID()); 
	    }
	    if (!subjectPublicKeyAlgID.equals(algID.getOID())) {
		if (debug != null) {
		    debug.println("X509CertSelector.match: "
		    	+ "subject public key alg IDs don't match");
		}
		return false;
	    }
	} catch (IOException e5) {
	    if (debug != null) {
		debug.println("X509CertSelector.match: IOException in subject "
		    + "public key algorithm OID check");
	    }
	    return false;
	}
	return true;
    }
    
    /* match on key usage extension value */
    private boolean matchKeyUsage(X509Certificate xcert) {
	if (keyUsage == null) {
	    return true;
	}
	boolean[] certKeyUsage = xcert.getKeyUsage();
	if (certKeyUsage != null) {
	    for (int keyBit = 0; keyBit < keyUsage.length; keyBit++) {
		if (keyUsage[keyBit] &&
		    ((keyBit >= certKeyUsage.length) || !certKeyUsage[keyBit])) {
		    if (debug != null) {
			debug.println("X509CertSelector.match: "
			    + "key usage bits don't match");
		    }
		    return false;
		}
	    }
	}
	return true;
    }
    
    /* match on extended key usage purpose OIDs */
    private boolean matchExtendedKeyUsage(X509Certificate xcert) {
	if ((keyPurposeSet == null) || keyPurposeSet.isEmpty()) {
	    return true;
	}
	try {
	    ExtendedKeyUsageExtension ext = 
		(ExtendedKeyUsageExtension)getExtensionObject(xcert, 
						EXTENDED_KEY_USAGE_ID);
	    if (ext != null) {
		Vector<ObjectIdentifier> certKeyPurposeVector = 
		    (Vector<ObjectIdentifier>)ext.get(ExtendedKeyUsageExtension.USAGES);
		if (!certKeyPurposeVector.contains(ANY_EXTENDED_KEY_USAGE) 
		        && !certKeyPurposeVector.containsAll(keyPurposeOIDSet)) {
		    if (debug != null) {
			debug.println("X509CertSelector.match: cert failed "
			    + "extendedKeyUsage criterion");
		    }
		    return false;
		}
	    }
	} catch (IOException ex) {
	    if (debug != null) {
		debug.println("X509CertSelector.match: "
		    + "IOException in extended key usage check");
	    }
	    return false;
	}
	return true;
    }
    
    /* match on subject alternative name extension names */
    private boolean matchSubjectAlternativeNames(X509Certificate xcert) {
	if ((subjectAlternativeNames == null) || subjectAlternativeNames.isEmpty()) {
	    return true;
	}
	try {
	    SubjectAlternativeNameExtension sanExt = 
		(SubjectAlternativeNameExtension) getExtensionObject(xcert, 
						      SUBJECT_ALT_NAME_ID); 
	    if (sanExt == null) {
		if (debug != null) {
		  debug.println("X509CertSelector.match: "
		      + "no subject alternative name extension");
		}
		return false;
	    }
	    GeneralNames certNames = (GeneralNames) 
		sanExt.get(SubjectAlternativeNameExtension.SUBJECT_NAME);
	    Iterator i = subjectAlternativeGeneralNames.iterator();
	    while (i.hasNext()) {
		GeneralNameInterface matchName = (GeneralNameInterface) i.next();
		boolean found = false;
		for (Iterator t = certNames.iterator(); t.hasNext() && !found; ) {
		    GeneralNameInterface certName =
			((GeneralName)t.next()).getName();
		    found = certName.equals(matchName);
		}
		if (!found && (matchAllSubjectAltNames || !i.hasNext())) {
		    if (debug != null) {
		      debug.println("X509CertSelector.match: subject alternative "
			  + "name " + matchName + " not found"); 
		    }
		    return false;
		} else if (found && !matchAllSubjectAltNames) {
		    break;
		}
	    }
	} catch (IOException ex) {
	    if (debug != null)
		debug.println("X509CertSelector.match: IOException in subject "
		    + "alternative name check");
	    return false;
	}
	return true;
    }
    
    /* match on name constraints */
    private boolean matchNameConstraints(X509Certificate xcert) {
	if (nc == null) {
	    return true;
	}
	try {
	    if (!nc.verify(xcert)) {
		if (debug != null) {
		    debug.println("X509CertSelector.match: "
		        + "name constraints not satisfied");
		}
		return false;
	    }
	} catch (IOException e) { 
	    if (debug != null) {
		debug.println("X509CertSelector.match: "
		    + "IOException in name constraints check");
	    }
	    return false; 
	}
	return true;
    }
    
    /* match on policy OIDs */
    private boolean matchPolicy(X509Certificate xcert) {
	if (policy == null) {
	    return true;
	}
	try {
	    CertificatePoliciesExtension ext = (CertificatePoliciesExtension)
	    	getExtensionObject(xcert, CERT_POLICIES_ID);
	    if (ext == null) {
		if (debug != null) {
		  debug.println("X509CertSelector.match: "
		      + "no certificate policy extension");
		}
		return false;
	    }
	    List<PolicyInformation> policies = (List<PolicyInformation>)ext.get(CertificatePoliciesExtension.POLICIES);
	    /*
	     * Convert the Vector of PolicyInformation to a Vector 
	     * of CertificatePolicyIds for easier comparison.
	     */
	    List<CertificatePolicyId> policyIDs = new ArrayList<CertificatePolicyId>(policies.size());
	    for (PolicyInformation info : policies) {
		policyIDs.add(info.getPolicyIdentifier());
	    }
	    if (policy != null) {
		boolean foundOne = false;
		/*
		 * if the user passes in an empty policy Set, then
		 * we just want to make sure that the candidate certificate
		 * has some policy OID in its CertPoliciesExtension
		 */
		if (policy.getCertPolicyIds().isEmpty()) {
		    if (policyIDs.isEmpty()) {
			if (debug != null) {
			    debug.println("X509CertSelector.match: "
			        + "cert failed policyAny criterion");
			}
			return false;
		    }
		} else {
		    for (CertificatePolicyId id : policy.getCertPolicyIds()) {
			if (policyIDs.contains(id)) {
			    foundOne = true;
			    break;
			}
		    }
		    if (!foundOne) {
			if (debug != null) {
			    debug.println("X509CertSelector.match: "
			        + "cert failed policyAny criterion");
			}
			return false;
		    }
		}
	    }
	} catch (IOException ex) {
	    if (debug != null) {
	        debug.println("X509CertSelector.match: "
		    + "IOException in certificate policy ID check");
	    }
	    return false;
	}
	return true;
    }
    
    /* match on pathToNames */
    private boolean matchPathToNames(X509Certificate xcert) {
	if (pathToGeneralNames == null) {
	    return true;
	}
	try {
	    NameConstraintsExtension ext = (NameConstraintsExtension)
	    	getExtensionObject(xcert, NAME_CONSTRAINTS_ID);
	    if (ext == null) {
		return true;
	    }
	    if ((debug != null) && debug.isOn("certpath")) {
		debug.println("X509CertSelector.match pathToNames:\n");
		Iterator i = pathToGeneralNames.iterator();
		while (i.hasNext()) {
		    debug.println("    " + i.next() + "\n");
		}
	    }
    
	    GeneralSubtrees permitted = (GeneralSubtrees)
		ext.get(NameConstraintsExtension.PERMITTED_SUBTREES);
	    GeneralSubtrees excluded = (GeneralSubtrees)
		ext.get(NameConstraintsExtension.EXCLUDED_SUBTREES);
	    if (excluded != null) {
		if (matchExcluded(excluded) == false) {
		    return false;
		}
	    }
	    if (permitted != null) {
		if (matchPermitted(permitted) == false) {
		    return false;
		}
	    }
	} catch (IOException ex) {
	    if (debug != null) {
		debug.println("X509CertSelector.match: "
		    + "IOException in name constraints check");
	    }
	    return false;
	}
	return true;
    }
   
    private boolean matchExcluded(GeneralSubtrees excluded) {
	/* 
	 * Enumerate through excluded and compare each entry
	 * to all pathToNames. If any pathToName is within any of the
	 * subtrees listed in excluded, return false.
	 */
	for (Iterator t = excluded.iterator(); t.hasNext(); ) {
	    GeneralSubtree tree = (GeneralSubtree)t.next();
	    GeneralNameInterface excludedName = tree.getName().getName();
	    Iterator i = pathToGeneralNames.iterator();
	    while (i.hasNext()) {
		GeneralNameInterface pathToName = (GeneralNameInterface) i.next();
		if (excludedName.getType() == pathToName.getType()) {
		    switch (pathToName.constrains(excludedName)) {
		    case GeneralNameInterface.NAME_WIDENS:
		    case GeneralNameInterface.NAME_MATCH:
			if (debug != null) {
			    debug.println("X509CertSelector.match: name constraints "
				+ "inhibit path to specified name");
			    debug.println("X509CertSelector.match: excluded name: " +
				pathToName);
			}
			return false;
		    default:
		    }
		}
	    }
	}
	return true;
    }
    
    private boolean matchPermitted(GeneralSubtrees permitted) {
	/*
	 * Enumerate through pathToNames, checking that each pathToName
	 * is in at least one of the subtrees listed in permitted.
	 * If not, return false. However, if no subtrees of a given type
	 * are listed, all names of that type are permitted.
	 */
	Iterator i = pathToGeneralNames.iterator();
	while (i.hasNext()) {
	    GeneralNameInterface pathToName = (GeneralNameInterface)i.next();
	    Iterator t = permitted.iterator();
	    boolean permittedNameFound = false;
	    boolean nameTypeFound = false;
	    String names = "";
	    while (t.hasNext() && !permittedNameFound) {
		GeneralSubtree tree = (GeneralSubtree)t.next();
		GeneralNameInterface permittedName = tree.getName().getName();
		if (permittedName.getType() == pathToName.getType()) {
		    nameTypeFound = true;
		    names = names + "  " + permittedName;
		    switch (pathToName.constrains(permittedName)) {
		    case GeneralNameInterface.NAME_WIDENS:
		    case GeneralNameInterface.NAME_MATCH:
			permittedNameFound = true;
			break;
		    default:
		    }
		}
	    }
	    if (!permittedNameFound && nameTypeFound) {
		if (debug != null)
		  debug.println("X509CertSelector.match: " + 
			    "name constraints inhibit path to specified name; " +
			    "permitted names of type " + pathToName.getType() +
			    ": " + names);
		return false;
	    }
	}
	return true;
    }
    
    /* match on basic constraints */
    private boolean matchBasicConstraints(X509Certificate xcert) {
	if (basicConstraints == -1) {
	    return true;
	}
	int maxPathLen = xcert.getBasicConstraints();
	if (basicConstraints == -2) {
	    if (maxPathLen != -1) {
		if (debug != null) {
		    debug.println("X509CertSelector.match: not an EE cert");
		}
		return false;
	    }
	} else {
	    if (maxPathLen < basicConstraints) {
		if (debug != null) {
		    debug.println("X509CertSelector.match: maxPathLen too small (" 
			+ maxPathLen + " < " + basicConstraints + ")"); 
		}
		return false;
	    }
	}
	return true;
    }
  
    private static Set<?> cloneSet(Set<?> set) {
	if (set instanceof HashSet) {
	    Object clone = ((HashSet<?>)set).clone();
	    return (Set<?>)clone;
	} else {
	    return new HashSet<Object>(set);
	}
    }

    /**
     * Returns a copy of this object.
     *
     * @return the copy
     */
    public Object clone() {
	try {
	    X509CertSelector copy = (X509CertSelector)super.clone();
	    // Must clone these because addPathToName et al. modify them
	    if (subjectAlternativeNames != null) {
		copy.subjectAlternativeNames = 
			(Set<List<?>>)cloneSet(subjectAlternativeNames);
		copy.subjectAlternativeGeneralNames = 
			(Set<GeneralNameInterface>)cloneSet
				(subjectAlternativeGeneralNames);
	    }
	    if (pathToGeneralNames != null) {
		copy.pathToNames = 
			(Set<List<?>>)cloneSet(pathToNames);
		copy.pathToGeneralNames = 
			(Set<GeneralNameInterface>)cloneSet
				(pathToGeneralNames);
	    }
	    return copy;
	} catch (CloneNotSupportedException e) {
	    /* Cannot happen */
	    throw new InternalError(e.toString());
	}
    }
}
